package com.ihr360.payroll.service.payroll.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ihr360.alps.api.ServiceProvider;
import com.ihr360.authority.sharing.annonation.AccessControl;
import com.ihr360.authority.sharing.service.AuthUserResourceService;
import com.ihr360.commons.context.session.Ihr360SessionContextHolder;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.type.AccessPrivilege;
import com.ihr360.commons.model.type.FieldSource;
import com.ihr360.commons.model.type.FormulaFrom;
import com.ihr360.commons.model.type.SalaryAdjustFormula;
import com.ihr360.commons.request.Ihr360SearchBody;
import com.ihr360.event.annonation.RestEventHandler;
import com.ihr360.expr.ExprException;
import com.ihr360.expr.core.Expr;
import com.ihr360.expr.core.ExprVariable;
import com.ihr360.expr.parser.ExprToken;
import com.ihr360.expr.utils.ExprUtils;
import com.ihr360.payroll.config.PayrollConfigEntity;
import com.ihr360.payroll.dao.AttendancePlanRepository;
import com.ihr360.payroll.dao.PayrollConfigRepository;
import com.ihr360.payroll.dao.PayslipConfigRepository;
import com.ihr360.payroll.dao.SalaryFieldRepository;
import com.ihr360.payroll.dao.SalaryPlanRepository;
import com.ihr360.payroll.dao.SystemSalaryFieldRepository;
import com.ihr360.payroll.entity.staff.CompanySalaryProfileEntity;
import com.ihr360.payroll.entity.staff.SalaryProfileFieldEntity;
import com.ihr360.payroll.model.dto.attendance.plan.AttendancePlan;
import com.ihr360.payroll.model.dto.attendance.rule.overtime.SyntheticalOverTimeRule;
import com.ihr360.payroll.model.dto.salary.basecode.SalaryFieldType;
import com.ihr360.payroll.model.dto.salary.basecode.SalaryTaxType;
import com.ihr360.payroll.model.dto.salary.embeddable.SalaryFieldValue;
import com.ihr360.payroll.model.dto.salary.embeddable.SystemSalaryFieldValue;
import com.ihr360.payroll.model.dto.salary.meta.PayrollConfig;
import com.ihr360.payroll.model.dto.salary.meta.SalaryField;
import com.ihr360.payroll.model.dto.salary.meta.SalaryFieldCode;
import com.ihr360.payroll.model.dto.salary.meta.system.SystemSalaryField;
import com.ihr360.payroll.model.dto.salary.plan.SalaryPlan;
import com.ihr360.payroll.model.dto.salary.profile.CompanySalaryProfile;
import com.ihr360.payroll.model.dto.staff.SalaryProfileField;
import com.ihr360.payroll.service.eventhandler.PayrollConfigRestEventListener;
import com.ihr360.payroll.service.payroll.PayrollConfigService;
import com.ihr360.payroll.service.payroll.convertor.PayrollConfigConvertor;
import com.ihr360.payroll.service.reference.SalaryReferenceService;
import com.ihr360.payroll.service.reference.impl.SalaryReferenceServiceImpl;
import com.ihr360.payroll.service.salaryplan.SalaryPlanService;
import com.ihr360.payroll.service.salaryprofile.CompanySalaryProfileService;
import com.ihr360.payroll.service.salarytask.SalaryTaskService;
import com.ihr360.payroll.service.salarytasklet.model.SimplePayrollConfig;
import com.ihr360.payroll.util.SalaryItemSystemNameUtils;
import com.ihr360.rest.reposity.SimpleRestReposity;
import com.ihr360.rest.service.AbstractSimpleRestServiceImpl;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

/**
 * @author tomtao
 */
@Service
@RestEventHandler(PayrollConfigRestEventListener.class)
@AccessControl()
public class PayrollConfigServiceImpl extends AbstractSimpleRestServiceImpl<PayrollConfig, Long> implements PayrollConfigService {
    final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    PayrollConfigRepository payrollConfigRepository;
    @Autowired
    SystemSalaryFieldRepository systemSalaryFieldRepository;
    @Autowired
    SalaryFieldRepository salaryFieldRepository;
    @Autowired
    AttendancePlanRepository attendancePlanRepository;
    @Autowired
    SalaryPlanRepository salaryPlanRepository;
    @Autowired
    PayrollConfigConvertor payrollConfigConvertor;
    @Autowired
    SalaryTaskService salaryTaskService;
    @Autowired
    SalaryReferenceService salaryReferenceService;
    @Autowired
    CompanySalaryProfileService companySalaryProfileService;
    @Autowired
    AuthUserResourceService authUserResourceService;
    @Autowired
    SalaryPlanService salaryPlanService;
    @Autowired
    PayslipConfigRepository payslipConfigRepository;


    @Override
    public SimpleRestReposity<PayrollConfig, Long> getSimpleRestReposity() {
        return payrollConfigRepository;
    }

    @Override
    public PayrollConfigEntity resetPayrollConfigAsSystem(Long configId) {

        PayrollConfig payrollConfig = payrollConfigRepository.findOne(configId);

        ValidationUtils.rejectIfEmpty(payrollConfig, null, "工资表不存在");

        List<SalaryField> fields = payrollConfig.getFields();
        ValidationUtils.rejectIfEmpty(fields, null, "不存在需要回复的薪资配置项目");
        Iterable<SystemSalaryField> systemSalaryFields = systemSalaryFieldRepository.findAll();
        ValidationUtils.rejectIfEmpty(systemSalaryFields, null, "系统薪资配置项目不存在");
        Map<String, SystemSalaryField> systemSalaryFieldMap = new HashMap<>();
        systemSalaryFields.forEach(t -> systemSalaryFieldMap.put(t.getCode(), t));
        fields.stream().forEach(f -> {
            SystemSalaryField sf = systemSalaryFieldMap.get(f.getCode());
            if (sf != null) {
                f.setName(sf.getName());
                SalaryFieldValue value = f.getValueMeta();
                if (value == null && sf.getValueMeta() != null) {
                    value = new SalaryFieldValue();
                }
                if (sf.getValueMeta() != null) {
                    BeanUtils.copyProperties(sf.getValueMeta(), value);
                    f.setValueMeta(value);
                    f.setSource(sf.getValueMeta().getDefaultSource());
                } else {
                    f.setValueMeta(null);
                }
                f.setActualPayer(sf.getActualPayer());
                f.setPayer(sf.getPayer());
                f.setCompanyCostNumOp(sf.getCompanyCostNumOp());
                f.setPersonalSalaryNumOp(sf.getPersonalSalaryNumOp());
                f.setTaxType(sf.getTaxType());
                f.setType(sf.getType());
            }
        });
        payrollConfig.setFields(fields);
        PayrollConfig savedConfig = payrollConfigRepository.save(payrollConfig);
        return payrollConfigConvertor.convertDtoToEntity(savedConfig);
    }

    @Override
    public PayrollConfigEntity getOneEntity(Long configId) {
        PayrollConfig loadedConfig = super.getOne(configId);
        ValidationUtils.rejectIfEmpty(loadedConfig, null, "无法获取工资表配置");
        return payrollConfigConvertor.convertDtoToEntity(loadedConfig);
    }

    @Override
    public PayrollConfig edit(Long configId) {
        PayrollConfig payrollConfig = super.getOne(configId);
        if (CollectionUtils.isEmpty(payrollConfig.getFields())) {
            return payrollConfig;
        }
        List<SalaryField> fields = getProcessedSalaryFields(Ihr360SessionContextHolder.getCompanyId(), configId);
        payrollConfig.setFields(fields);
        return payrollConfig;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayrollConfig saveOrUpdate(PayrollConfig payrollConfig) {
        if (payrollConfig.getId() != null) {
            PayrollConfig ret = payrollConfigRepository.findOne(payrollConfig.getId());
            if (ret.isSystemTemplate()) {
                throw new Ihr360Exception("", "您没有权限操作");
            }
            salaryFieldConstraintHandle(ret, payrollConfig);
            payrollConfig.setCreatedAt(ret.getCreatedAt());
            payrollConfig.setUpdatedAt(ret.getUpdatedAt());
        } else {
            if (payrollConfig.isSystemTemplate()) {
                throw new Ihr360Exception("", "您没有权限操作");
            }
        }
        if (CollectionUtils.isEmpty(payrollConfig.getFields())) {
            return super.createOrUpdate(payrollConfig.getId(), payrollConfig);
        }
        payrollConfig = setPayrollConfig(payrollConfig);
        return super.createOrUpdate(payrollConfig.getId(), payrollConfig);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayrollConfig saveOrUpdateSystemTemplate(PayrollConfig payrollConfig) {
        if (payrollConfig.getId() != null) {
            PayrollConfig ret = payrollConfigRepository.findOne(payrollConfig.getId());
            if (!ret.isSystemTemplate()) {
                throw new Ihr360Exception("", "您没有权限操作");
            }
            salaryFieldConstraintHandle(ret, payrollConfig);
            payrollConfig.setCreatedAt(ret.getCreatedAt());
            payrollConfig.setUpdatedAt(ret.getUpdatedAt());
        } else {
            if (!payrollConfig.isSystemTemplate()) {
                throw new Ihr360Exception("", "您没有权限操作");
            }
        }
        if (CollectionUtils.isEmpty(payrollConfig.getFields())) {
            return payrollConfigRepository.save(payrollConfig);
        }
        payrollConfig = setPayrollConfig(payrollConfig);
        return payrollConfigRepository.save(payrollConfig);
    }

    /**
     * 薪资字段约束处理
     *
     * @param ret
     * @param payrollConfig
     */
    private void salaryFieldConstraintHandle(PayrollConfig ret, PayrollConfig payrollConfig) {
        Map<String, Long> salaryFieldCodeMap = new HashMap<>();
        List<Long> deletedFieldIds = new ArrayList<>();
        List<String> fieldIds = payrollConfig.getFields().stream().map(SalaryField::getCode).collect(toList());
        ret.getFields().forEach(field -> {
            salaryFieldCodeMap.put(field.getCode(), field.getId());
            if (!fieldIds.contains(field.getCode())) {
                deletedFieldIds.add(field.getId());
            }
        });

        // 设置已经存在的字段ID
        payrollConfig.getFields().forEach(salaryField -> {
            if (salaryFieldCodeMap.containsKey(salaryField.getCode())) {
                salaryField.setId(salaryFieldCodeMap.get(salaryField.getCode()));
            }
        });

        // 将删除的字段在工资单中去除
        if (!CollectionUtils.isEmpty(deletedFieldIds)) {
            payslipConfigRepository.deletePayslipConfigFieldsByFieldId(deletedFieldIds);

            attendancePlanRepository.deletePersonalLeaveRuleReferenceFields(deletedFieldIds);
            attendancePlanRepository.deleteOvertimeRuleReferenceFields(deletedFieldIds);
            attendancePlanRepository.deleteEnrollAdjustRuleReferenceFields(deletedFieldIds);
            attendancePlanRepository.deleteSickLeaveRuleReferenceFields(deletedFieldIds);
            attendancePlanRepository.deleteSyntheticalOvertimeruleReferenceFields(deletedFieldIds);
        }
    }

    private PayrollConfig setPayrollConfig(PayrollConfig payrollConfig) {
        List<SalaryField> salaryAdjustReferenceFields = new ArrayList<>();
        List<String> salaryAdjustReferenceFieldCodes = new ArrayList<>();
        if (!CollectionUtils.isEmpty(payrollConfig.getSalaryAdjustReferenceFields())) {
            salaryAdjustReferenceFieldCodes = payrollConfig.getSalaryAdjustReferenceFields().
                    stream().map(SalaryField::getCode).collect(toList());
            payrollConfig.setSalaryAdjustReferenceFields(salaryAdjustReferenceFields);
        }
        payrollConfig.getFields().forEach(salaryField -> salaryField.setConfig(payrollConfig));
        Map<String, SalaryField> salaryFieldMap = payrollConfig.getFields().stream()
                .collect(toMap(SalaryField::getCode, Function.identity()));
        Set<String> systemSalaryFieldCode = salaryFieldMap.keySet();
        List<SystemSalaryField> systemSalaryFields = systemSalaryFieldRepository.findByCodeIn(Lists.newArrayList(systemSalaryFieldCode));
        String companyId = Ihr360SessionContextHolder.getCompanyId();
        CompanySalaryProfile companySalaryProfile = companySalaryProfileService.getOrCreateByCompanyId(companyId);
        List<SalaryProfileField> companySalaryProfileds = companySalaryProfile.getProfileFields();
        Map<String, String> companySalaryReferenceFormulaMap = companySalaryProfileds.stream()
                .collect(Collectors.toMap(SalaryProfileField::getCode, SalaryProfileField::getCompanySalaryReferenceFormula));

        Map<String, SystemSalaryFieldValue> systemSalaryFieldMap = Maps.newHashMap();
        systemSalaryFields.forEach(systemSalaryField ->
                systemSalaryFieldMap.put(systemSalaryField.getCode(), systemSalaryField.getValueMeta())
        );

        Set<String> statisticsFieldCodes = payrollConfig.getFields().stream().filter(s -> SalaryFieldType.Code.statistics.equals(s.getType().getCode()) && s.getCode().endsWith(SalaryFieldType.TOTAL))
                .map(s -> s.getCode()).collect(Collectors.toSet());

        Map<String, List<SalaryField>> statisticsFieldMaps = payrollConfig.getFields().stream().filter(s -> statisticsFieldCodes.contains(s.getType().getCode() + SalaryFieldType.TOTAL))
                .collect(Collectors.groupingBy(s -> s.getType().getCode() + SalaryFieldType.TOTAL));

        for (SalaryField salaryField : payrollConfig.getFields()) {
            String code = salaryField.getCode();
            if (salaryAdjustReferenceFieldCodes.contains(code)) {
                salaryAdjustReferenceFields.add(salaryField);
            }
            salaryField.setConfig(payrollConfig);
            SystemSalaryFieldValue systemFieldValue = systemSalaryFieldMap.get(code);
            String companyReferenceFormula = companySalaryReferenceFormulaMap.get(code);

            String formula = salaryField.getValueMeta().getFormula();

            boolean formulaEquals = true;
            //是自动生成的统计项目
            if (SalaryFieldType.Code.statistics.equals(salaryField.getType().getCode()) && salaryField.getCode().endsWith(SalaryFieldType.TOTAL)) {
                List<SalaryField> statisticFields = statisticsFieldMaps.get(salaryField.getCode());
                if ((StringUtils.isEmpty(formula) || formula.equals(ExprToken.EQUAL.val))) {
                    formulaEquals = true;
                } else {
                    if (CollectionUtils.isEmpty(statisticFields)) {
                        formulaEquals = false;
                    } else {
                        // 判断统计项公式
                        formula = formula.replaceAll("\\s*|\t|\r|\n", "");
                        Expr ideographicExpr = null;
                        try {
                            ideographicExpr = ExprUtils.parseExpression(formula);
                        } catch (ExprException e) {
                            throw new Ihr360Exception(null, String.format("变量:%s 表达式:%s 解析异常:%s", e.getVariableId(), e.getExpression(), e.getCause().getMessage()));
                        }

                        ExprVariable[] exprVariables = ExprVariable.findVariables(ideographicExpr);
                        if (CollectionUtils.isEmpty(exprVariables) || exprVariables.length != statisticFields.size()) {
                            formulaEquals = false;
                        }
                        List<String> salaryFieldCodes = statisticFields.stream().map(SalaryField::getCode).collect(Collectors.toList());
                        for (ExprVariable exprVariable : exprVariables) {
                            if (!salaryFieldCodes.contains(exprVariable.getName())) {
                                formulaEquals = false;
                                break;
                            }
                        }

                    }
                }

            } else {
                if (systemFieldValue == null) {
                    salaryField.getValueMeta().setFormulaFrom(FormulaFrom.SYS);
                    continue;
                }

                formulaEquals = (StringUtils.isEmpty(formula) && (systemFieldValue == null || StringUtils.isEmpty(systemFieldValue.getFormula())))
                        || (StringUtils.isNotEmpty(formula) && systemFieldValue != null && formula.equals(systemFieldValue.getFormula()));
            }

            boolean allInputType = salaryField.getSource() == FieldSource.INPUT && systemFieldValue != null && systemFieldValue.getDefaultSource() == FieldSource.INPUT;
            boolean allFunctionType = salaryField.getSource() == FieldSource.FUNCTION && systemFieldValue != null && systemFieldValue.getDefaultSource() == FieldSource.FUNCTION;

            //都是输入项
            if (allInputType) {
                if (StringUtils.isNotEmpty(companyReferenceFormula)) {
                    salaryField.getValueMeta().setFormulaFrom(FormulaFrom.USR);
                } else {
                    salaryField.getValueMeta().setFormulaFrom(FormulaFrom.SYS);
                }
                //都是公式项或 系统是输入项且薪资表是公式项（公司薪资档案）
            } else if (allFunctionType || (systemFieldValue != null && systemFieldValue.getDefaultSource() == FieldSource.INPUT && salaryField.getSource() == FieldSource.FUNCTION)) {

                if (StringUtils.isNotEmpty(companyReferenceFormula)) {
                    if (companyReferenceFormula.equals(formula) || StringUtils.isEmpty(formula)) {
                        salaryField.getValueMeta().setFormulaFrom(FormulaFrom.SYS);
                        salaryField.getValueMeta().setFormula(companyReferenceFormula);
                    } else {
                        salaryField.getValueMeta().setFormulaFrom(FormulaFrom.USR);
                    }
                } else if (formulaEquals) {
                    salaryField.getValueMeta().setFormulaFrom(FormulaFrom.SYS);

                } else {
                    salaryField.getValueMeta().setFormulaFrom(FormulaFrom.USR);
                }

            } else {
                salaryField.getValueMeta().setFormulaFrom(FormulaFrom.USR);
            }
        }

        payrollConfig.setSalaryAdjustReferenceFields(salaryAdjustReferenceFields);
        return payrollConfig;
    }

    @Override
    public List<PayrollConfig> list() {
        Map<Long, Integer> idsPrivilegeMap = authUserResourceService.queryUserResourcesPrivilegeWithLongId(PayrollConfig.class);
        if (CollectionUtils.isEmpty(idsPrivilegeMap)) {
            return Collections.emptyList();
        }

        List<PayrollConfig> configs = payrollConfigRepository.findByIdIn(idsPrivilegeMap.keySet());
        if (CollectionUtils.isEmpty(configs)) {
            return Collections.emptyList();
        }
        configs.forEach(s -> {
            Integer privilege = idsPrivilegeMap.get(s.getId());
            s.getResourceAccess().setAccessPrivilege(AccessPrivilege.getAccessPrivilege(privilege));
        });
        return configs;
    }

    @Override
    public List<SalaryField> getSalaryFieldByConfigIdAndTypeCategoryId(Long configId, Long categoryId) {
        return salaryFieldRepository.findByConfigIdAndTypeCategoryId(configId, categoryId);
    }

    @Override
    public boolean isReferenced(Long configId, boolean haveEnable) {
        return haveEnable ?
                (attendancePlanRepository.countByEnabledAndPayrollConfigId(true, configId) > 0 ||
                        salaryPlanRepository.countByEnabledAndPayrollConfigId(true, configId) > 0)
                : (attendancePlanRepository.countByPayrollConfigId(configId) > 0 ||
                salaryPlanRepository.countByPayrollConfigId(configId) > 0);
    }

    @Override
    public List<SalaryPlan> getIsPayPeriodSalaryPlanList(String companyId, Long configId) {
        return salaryTaskService.getActivePayrollConfigIds(companyId, configId);
    }

    @Override
    public List<SalaryField> getProcessedSalaryFieldsWithOutAuth(String companyId, Long payrollconfigId) {
        PayrollConfig payrollConfig = payrollConfigRepository.findOne(payrollconfigId);
        return getProcessedSalaryFields(companyId, payrollConfig);
    }

    @Override
    public SimplePayrollConfig getSimplePayRollConfig(String companyId, Long payrollconfigId) {
        PayrollConfig payrollConfig = super.getOne(payrollconfigId);
        List<SalaryField> processedSalaryFields = getProcessedSalaryFields(companyId, payrollConfig);
        return new SimplePayrollConfig(payrollConfig.getSalaryAdjustFormula(), payrollConfig.getSalaryAdjustReferenceFields(), processedSalaryFields);
    }

    @Override
    public List<SalaryField> getProcessedSalaryFields(String companyId, Long payrollconfigId) {
        PayrollConfig payrollConfig = super.getOne(payrollconfigId);
        return getProcessedSalaryFields(companyId, payrollConfig);
    }

    private List<SalaryField> getProcessedSalaryFields(String companyId, PayrollConfig config) {
        ValidationUtils.rejectIfEmpty(config, null, "无法获取薪资表配置");
        List<SalaryField> fields = config.getFields();
        if (CollectionUtils.isEmpty(fields)) {
            return Collections.emptyList();
        }
        CompanySalaryProfile companySalaryProfile = companySalaryProfileService.getCompanySalaryProfile(companyId);
        if (companySalaryProfile == null) {
            return fields;
        }
        List<SalaryProfileField> profileFields = companySalaryProfile.getProfileFields();
        if (CollectionUtils.isEmpty(profileFields)) {
            return fields;
        }
        Set<String> collect = profileFields.stream().map(s -> s.getCode()).collect(toSet());

        fields.stream().forEach(s -> {
            if (collect.contains(s.getCode())) {
                SalaryFieldValue fieldValue = s.getValueMeta();
                if (!FormulaFrom.USR.equals(fieldValue.getFormulaFrom())) {
                    StringBuilder builder = new StringBuilder();
                    builder.append(ExprToken.EQUAL.val);
                    builder.append(ServiceProvider.SERVICE_ID.STAFF_SALARY);
                    builder.append(ExprToken.NS_FIELD_CONNECTOR.val);
                    builder.append(s.getCode());
                    fieldValue.setFormula(builder.toString());
                    s.setSource(FieldSource.FUNCTION);
                }
            }
            //对薪资项目设置纳税类型为类别的纳税类型
            SalaryFieldType type = s.getType();
            if (type != null) {
                List<SalaryTaxType> salaryTaxTypes = type.getSalaryTaxTypes();
                if (CollectionUtils.isNotEmpty(salaryTaxTypes)) {
                    s.setTaxType(salaryTaxTypes.get(0));
                } else {
                    s.setTaxType(null);
                }
            }
        });

        //设置分段薪资调整
        SalaryAdjustFormula salaryAdjustFormula = config.getSalaryAdjustFormula();
        if (salaryAdjustFormula != null) {
            Optional<SalaryField> fieldOptional = fields.stream().filter(s -> SalaryFieldCode.PHASED_SALARY_ADJUSTMENT.equals(s.getCode())).findFirst();

            if (fieldOptional.isPresent()) {
                SalaryField field = fieldOptional.get();
                SalaryFieldValue fieldValue = field.getValueMeta();
                fieldValue.setFormula(salaryAdjustFormula.getFormula());
                field.setSource(FieldSource.FUNCTION);
                field.setValueMeta(fieldValue);
            }
        }

        //生成合计公式项目
        Map<String, SalaryField> staticsFieldMap = fields.stream().filter(s -> s.getType() != null && SalaryFieldType.Code.statistics.equals(s.getType().getCode())).collect(toMap(SalaryField::getCode, s -> s));

        Map<SalaryFieldType, List<SalaryField>> salaryFieldTypeMap = fields.stream()
                .filter(s -> s.getType() == null || !SalaryFieldType.Code.statistics.equals(s.getType().getCode()))
                .collect(groupingBy(s -> s.getType()));

        for (Map.Entry<SalaryFieldType, List<SalaryField>> entry : salaryFieldTypeMap.entrySet()) {
            SalaryFieldType type = entry.getKey();
            List<SalaryField> sfs = entry.getValue();
            if (type.isGenTypeTotalField() && CollectionUtils.isNotEmpty(sfs)) {
                //生成分类统计项目
                String code = type.totalCode();
                SalaryField statisticsField = staticsFieldMap.get(code);
                if (statisticsField != null) {
                    SalaryFieldValue valueMeta = statisticsField.getValueMeta();
                    if (FormulaFrom.USR.equals(valueMeta.getFormulaFrom())) {
                        continue;
                    }
                    StringBuilder builder = new StringBuilder();
                    StringBuilder namebuilder = new StringBuilder();
                    builder.append(ExprToken.EQUAL.val);
                    namebuilder.append(ExprToken.EQUAL.val);
                    for (int index = 0; index < sfs.size(); index++) {
                        SalaryField field = sfs.get(index);
                        if (index == 0) {
                            builder.append(field.getCode());
                            namebuilder.append(field.getName());
                        } else {
                            builder.append(ExprToken.PLUS.val);
                            builder.append(field.getCode());
                            namebuilder.append(ExprToken.PLUS.val);
                            namebuilder.append(field.getName());
                        }
                    }
                    String formula = builder.toString();
                    String nameFormula = namebuilder.toString();
                    if (!formula.equals(valueMeta.getFormula())) {
                        valueMeta.setFormula(formula);
                        valueMeta.setFormulaFrom(FormulaFrom.CALC);
                        statisticsField.setValueMeta(valueMeta);
                        logger.info("[薪资表]", "动态生成公式 {} {}", statisticsField.getName(), nameFormula);
                    }
                }
            }
        }
        return fields;

    }

    @Override
    public boolean isDuplicateName(String name, Long configId) {
        String companyId = Ihr360SessionContextHolder.getCompanyId();
        return configId != null ? payrollConfigRepository.countByCompanyIdAndIdNotAndName(companyId, configId, name) > 0
                : payrollConfigRepository.countByCompanyIdAndName(companyId, name) > 0;
    }

    @Override
    public boolean isDuplicateNameSystemTemplate(String name, Long configId) {
        return configId != null ? payrollConfigRepository.countByIsSystemTemplateAndIdNotAndName(true, configId, name) > 0
                : payrollConfigRepository.countByIsSystemTemplateAndName(true, name) > 0;
    }

    @Override
    public JSONObject resetFormularToSystem(Long salaryFieldId) {
        JSONObject result = new JSONObject();
        result.put("success", true);
        ValidationUtils.rejectIfEmpty(salaryFieldId, null, "数据异常，薪资项目id不能为空！");
        SalaryField salaryField = salaryFieldRepository.findOne(salaryFieldId);
        ValidationUtils.rejectIfEmpty(salaryField, null, "薪资项目不存在！");
        String code = salaryField.getCode();
        SystemSalaryField systemSalaryField = systemSalaryFieldRepository.findByCode(code);
        ValidationUtils.rejectIfEmpty(systemSalaryField, null, "系统薪资项目不存在!");

        CompanySalaryProfileEntity companySalaryProfileEntity = companySalaryProfileService.getByCompanyId(Ihr360SessionContextHolder.getCompanyId());
        List<SalaryProfileFieldEntity> companySalaryProfileFields = companySalaryProfileEntity.getProfileFields();
        Map<String, String> companySalaryRefFormulaMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(companySalaryProfileFields)) {
            companySalaryRefFormulaMap = companySalaryProfileFields.stream().collect(Collectors.toMap(SalaryProfileFieldEntity::getCode, SalaryProfileFieldEntity::getCompanyReferenceFormula));
        }

        String formula = companySalaryRefFormulaMap.get(code);
        if (StringUtils.isEmpty(formula)) {
            salaryField.getValueMeta().setFormula(systemSalaryField.getValueMeta().getFormula());
            salaryField.getValueMeta().setFormulaFrom(FormulaFrom.SYS);
            salaryFieldRepository.save(salaryField);
            formula = systemSalaryField.getValueMeta().getFormula();
        }
        result.put("formula", formula);
        if (StringUtils.isNotEmpty(formula)) {
            String formulastr = salaryReferenceService.parseCodeToIdeographicFormular(formula)
                    .getString(SalaryReferenceServiceImpl.FORMULAR_KEY);
            result.put("formulaStr", formulastr);
        }
        return result;
    }

    @Override
    public Page<PayrollConfig> searchSystemTemplate(Ihr360SearchBody<PayrollConfig> searchBody) {
        Specification<PayrollConfig> specification = (root, cq, cb) -> {
            Predicate predicate = cb.conjunction();
            List<Expression<Boolean>> expressionList = predicate.getExpressions();
            expressionList.add(cb.equal(root.get(PayrollConfig.Field.IS_SYSTEM_TEMPLATE).as(boolean.class), true));
            if (searchBody.getSpecification() != null) {
                Predicate searchPredicate = searchBody.getSpecification().toPredicate(root, cq, cb);
                if (searchPredicate != null) {
                    return cb.and(predicate, searchBody.getSpecification().toPredicate(root, cq, cb));
                }
            }
            return predicate;
        };
        return payrollConfigRepository.findAll(specification, searchBody.getPageable());
    }

    @Override
    public PayrollConfig getOneSystemTemplate(Long id) {
        PayrollConfig payrollConfig = payrollConfigRepository.findOne(id);
        if (payrollConfig == null) {
            return null;
        }
        if (!payrollConfig.isSystemTemplate()) {
            throw new Ihr360Exception("", "您没有权限操作.");
        }
        List<SalaryField> fields = getProcessedSalaryFields(Ihr360SessionContextHolder.getCompanyId(), payrollConfig);
        payrollConfig.setFields(fields);
        return payrollConfig;
    }

    @Override
    public boolean deleteSystemTemplateById(Long id) {
        PayrollConfig payrollConfig = payrollConfigRepository.findOne(id);
        if (payrollConfig == null) {
            return true;
        }
        if (!payrollConfig.isSystemTemplate()) {
            throw new Ihr360Exception("", "您没有权限操作");
        }
        payrollConfigRepository.delete(id);
        return true;
    }

    @Override
    public boolean deleteById(Long id) {
        PayrollConfig payrollConfig = super.getOne(id);
        if (payrollConfig == null) {
            return true;
        }
        if (payrollConfig.isSystemTemplate()) {
            throw new Ihr360Exception("", "您没有权限操作");
        }
        super.deleteOne(id);
        return true;
    }

    @Override
    public PayrollConfig getOneContainSystemTemplate(Long id) {
        PayrollConfig payrollConfig = payrollConfigRepository.findOne(id);
        if (payrollConfig.isSystemTemplate()) {
            return payrollConfig;
        }
        payrollConfig = super.getOne(id);
        List<SalaryField> fields = getProcessedSalaryFields(Ihr360SessionContextHolder.getCompanyId(), payrollConfig);
        payrollConfig.setFields(fields);
        return payrollConfig;
    }

    @Override
    public PayrollConfig clonePayrollConfig(Long id) {
        String companyId = Ihr360SessionContextHolder.getCompanyId();
        PayrollConfig clonedPayrollConfig = getOneContainSystemTemplate(id);
        if (clonedPayrollConfig == null) {
            throw new Ihr360Exception("", "没有获取到薪资表");
        }

        // 1.封装新的薪资表
        PayrollConfig payrollConfig = new PayrollConfig();
        BeanUtils.copyProperties(clonedPayrollConfig, payrollConfig);
        payrollConfig.setId(null);
        payrollConfig.setSystemTemplate(false);
        payrollConfig.setCompanyId(companyId);

        List<String> salaryAdjustReferenceFieldCodes = payrollConfig.getSalaryAdjustReferenceFields().
                stream().map(SalaryField::getCode).collect(toList());

        List<SalaryField> fields = new ArrayList<>();
        List<SalaryField> salaryAdjustReferenceFields = new ArrayList<>();
        payrollConfig.getFields().forEach(salaryField -> {
            SalaryField field = new SalaryField();
            copySalaryField(salaryField, field);
            field.setConfig(payrollConfig);
            if (salaryAdjustReferenceFieldCodes.contains(field.getCode())) {
                salaryAdjustReferenceFields.add(field);
            }
            fields.add(field);
        });
        payrollConfig.setSalaryAdjustReferenceFields(salaryAdjustReferenceFields);
        payrollConfig.setFields(fields);

        // 2.生成新的薪资表名称
        String name;
        while (true) {
            // 获取不重复名称
            name = SalaryItemSystemNameUtils.getSystemName(payrollConfig.getName());
            if (!isDuplicateName(name, null)) {
                break;
            }
        }
        payrollConfig.setName(name);

        // 3.保存
        return super.create(payrollConfig);
    }

    @Override
    public List<Long> getSystemTemplateIdList() {
        List<PayrollConfig> payrollConfigList = payrollConfigRepository.findByIsSystemTemplate(true);
        return payrollConfigList.stream().map(PayrollConfig::getId).collect(toList());
    }

    private void copySalaryField(SalaryField source, SalaryField target) {
        BeanUtils.copyProperties(source, target);
        target.setId(null);
    }

}
